#region File Header
//-----------------------------------------------------------------------------
// SibLib
//
// Copyright (C) 2010 Julien Villers
// This program is distributed under the terms of the 
// GNU Lesser General Public License (LGPL).
// See Docs/lgpl.txt and Docs/gpl.txt
//
// This file is based on the NetRumble starter kit (C) Microsoft.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace SibLib.Particles
{
    /// <summary>
    /// The manager behind all particle code.
    /// </summary>
    public class ParticleEffectManager          // TODO: GameComponent
    {
        #region Effect Collection Data

        /// <summary>
        /// Cache of registered particle effects.
        /// </summary>
        private Dictionary<int, List<ParticleEffect>> particleEffectCache = new Dictionary<int, List<ParticleEffect>>();

        /// <summary>
        /// Active particle effects.
        /// </summary>
        private Containers.AutoList<ParticleEffect> activeParticleEffects = new Containers.AutoList<ParticleEffect>();


        #endregion

        #region Graphics Data


        /// <summary>
        /// The content manager used to load textures in the particle systems.
        /// </summary>
        private ContentManager contentManager;


        #endregion

        #region Initialization Methods


        /// <summary>
        /// Construct a new particle-effect manager.
        /// </summary>
        public ParticleEffectManager(ContentManager contentManager)
        {
            // safety-check the parameters
            if (contentManager == null)
            {
                throw new ArgumentNullException("contentManager");
            }

            this.contentManager = contentManager;
        }


        #endregion

        #region Updating Methods


        /// <summary>
        /// Update the particle-effect manager.
        /// </summary>
        /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
        public void Update(float elapsedTime)
        {
            activeParticleEffects.ProcessAdds();
            foreach (ParticleEffect effect in activeParticleEffects)
            {
                if (!effect.Active)
                    continue;
                
                effect.Update(elapsedTime);
                    if (!effect.Active)
                        activeParticleEffects.Remove(effect);
            }
            activeParticleEffects.ProcessRemoves();
        }


        #endregion

        #region Drawing Methods


        /// <summary>
        /// Draw all of the particle effects in the manager.
        /// </summary>
        /// <param name="spriteBatch">The SpriteBatch object used to draw.</param>
        /// <param name="blendMode">Filters the systems drawn in this pass.</param>
        public virtual void Draw(SpriteBatch spriteBatch, SpriteBlendMode blendMode)
        {
            for (int i = 0; i < activeParticleEffects.Count; ++i)
            {
                if (activeParticleEffects[i].Active)
                {
                    activeParticleEffects[i].Draw(spriteBatch, blendMode);
                }
            }
        }


        #endregion

        #region Particle-Effect Creation Methods

        /// <summary>
        /// Spawn a new particle effect at a given location
        /// </summary>
        /// <param name="effectType">The effect in question.</param>
        /// <param name="position">The position of the effect.</param>
        /// <returns>The new particle effect.</returns>
        public ParticleEffect SpawnEffect(ParticleEffectType effectType,
            Vector2 position)
        {
            return SpawnEffect(effectType, position, null);
        }


        /// <summary>
        /// Spawn a new particle effect at a the position of a given gameplay object
        /// </summary>
        /// <param name="effectType">The effect in question.</param>
        /// <param name="gameplayObject">The gameplay object.</param>
        /// <returns>The new particle effect.</returns>
        public ParticleEffect SpawnEffect(ParticleEffectType effectType,
            Entities.GameplayObject gameplayObject)
        {
            // safety-check the parameter
            if (gameplayObject == null)
            {
                throw new ArgumentNullException("gameplayObject");
            }

            return SpawnEffect(effectType, gameplayObject.Position, gameplayObject);
        }


        /// <summary>
        /// Spawn a new particle effect at a given location and gameplay object
        /// </summary>
        /// <param name="effectType">The effect in question.</param>
        /// <param name="position">The position of the effect.</param>
        /// <param name="gameplayObject">The gameplay object.</param>
        /// <returns>The new particle effect.</returns>
        public ParticleEffect SpawnEffect(ParticleEffectType effectType,
            Vector2 position, Entities.GameplayObject gameplayObject)
        {
            ParticleEffect particleEffect = null;

            if (particleEffectCache.ContainsKey((int)effectType) == true)
            {
                List<ParticleEffect> availableSystems = particleEffectCache[(int)effectType];

                for (int i = 0; i < availableSystems.Count; ++i)
                {
                    if (availableSystems[i].Active == false)
                    {
                        particleEffect = availableSystems[i];
                        break;
                    }
                }

                if (particleEffect == null)
                {
                    particleEffect = availableSystems[0].Clone();
                    particleEffect.Initialize(contentManager);
                    availableSystems.Add(particleEffect);
                }
            }

            if (particleEffect != null)
            {
                particleEffect.Reset();
                particleEffect.GameplayObject = gameplayObject;
                particleEffect.Position = position;
                activeParticleEffects.Add(particleEffect);
            }

            return particleEffect;
        }


        #endregion

        #region Registration Methods

        /// <summary>
        /// Register a new type of particle effect with the manager.
        /// </summary>
        /// <param name="effectType">The enumeration associated with this type.</param>
        /// <param name="fileName">The path to the XML file to be deserialized.</param>
        /// <param name="initialCount">How many of these to pre-create.</param>
        public void RegisterParticleEffect(ParticleEffectType effectType,
            string fileName, int initialCount)
        {
            if (!particleEffectCache.ContainsKey((int)effectType))
            {
                ParticleEffect particleEffect = ParticleEffect.Load(fileName);
                particleEffect.Initialize(contentManager);
                particleEffect.Stop(true);
                particleEffectCache.Add((int)effectType, new List<ParticleEffect>());
                particleEffectCache[(int)effectType].Add(particleEffect);
                for (int i = 1; i < initialCount; i++)
                {
                    ParticleEffect cloneEffect = particleEffect.Clone();
                    cloneEffect.Initialize(contentManager);
                    cloneEffect.Stop(true);
                    particleEffectCache[(int)effectType].Add(cloneEffect);
                }
            }
        }



        /// <summary>
        /// Remove the given particle-effect type from the maanger.
        /// </summary>
        /// <param name="effectType">The enumeration to be cleared against.</param>
        public void UnregisterParticleEffect(ParticleEffectType effectType)
        {
            if (particleEffectCache.ContainsKey((int)effectType) == true)
            {
                for (int i = 0; i < particleEffectCache[(int)effectType].Count; ++i)
                {
                    activeParticleEffects.Remove(particleEffectCache[(int)effectType][i]);
                }
                particleEffectCache.Remove((int)effectType);
            }
        }


        #endregion
    }
}
